{%-import 'loader.jinja2' as loader with context-%}
{%-set std = resolver.cpp_resolve_namespace(['std']) %}
{%-set className = resolver.cpp_resolve_namespace(ns)+Name%}
{%macro NestedObjectName(name) -%}
    {{className}}::{{-name | UpperCamelCase-}}
{%-endmacro%}
{%-macro ObjectType(propName, propSchema) -%}
    {%-if '$ref' in propSchema -%}
        {{-loader.Reference(resolver, propSchema['$ref'])-}}
    {%-else-%}
        {{-NestedObjectName(propName)-}}
    {%-endif-%}
{%-endmacro%}
{%-set optionList -%}
{%-for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%}
{{ObjectType(optionName, s)}}{%if not loop.last%}, {%endif-%}
{%-endfor -%}
{%-endset%}
{%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%}

{%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%}
{{loader.Class('cpp', resolver, [className], optionName, s) }}
{%-endfor%}

{%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%}
{{className}}::{{Name}}(const {{ObjectType(optionName, s)}}& obj) : _value(obj)
{

}
{%endfor%}

{%set commonType = schema.GetCommonType(resolver)%}
{%-if commonType in ['boolean', 'integer', 'number', string] %}
{%for origNs in originalNamespace %}
namespace {{origNs}} {
{%-endfor%} 
{{std}}ostream& operator<<({{std}}ostream& os, const {{className}}& inst)
{
    {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%}
    {%if not loop.first%}
    else {%endif%}if (inst._value.which() == {{loop.index - 1}})
    {
        os << boost::get<{{ObjectType(optionName, s)}}>(inst._value);
    }
    {%-endfor %}
    return os;
}

{{std}}size_t hash_value(const {{className}}& inst)
{
    {{std}}size_t seed = 0;
    {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%}
    {%if not loop.first%}
    else {%endif%}if (inst._value.which() == {{loop.index - 1}})
    {
        boost::hash_combine(seed, boost::get<{{ObjectType(optionName, s)}}>(inst._value));
    }
    {%-endfor %}
    return seed;
}
{%for origNs in originalNamespace %}}{%-endfor%} // end namespaces
{%-endif%}

boost::variant<{{optionList}}> {{className}}::Get() const
{
    return _value;
}

void {{className}}::Set(const boost::variant<{{optionList}}>& variant)
{
    _value = variant;
    {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%}
    {%if not loop.first%}
    else {%endif%}if (_value.which() == {{loop.index - 1}})
    {
        boost::get<{{ObjectType(optionName, s)}}>(_value).SetHandle(_handle);
    }
    {%-endfor %}
}

{%-if commonType in ['boolean', 'integer', 'number', string] %}
{{className}} {{className}}::FromString(const std::string& str)
{
    {{exception}}Collection exceptionCollection;
    {%for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%}
    try
    {
        auto obj = {{ObjectType(optionName, s)}}::FromString(str);
        return {{className}}(obj);
    }
    catch (const std::exception& e)
    {
        // {{optionName}} wasnt able to deserialize{%if not loop.last%}
        // but there are other option to try{%endif%}
        exceptionCollection.AddException(e);
    }
    {%endfor %}

    // Didnt deserialize
    throw {{exception}}(exceptionCollection.what());
}
{%-endif%}

{{className}} {{className}}::FromJson(const rapidjson::Value& json)
{
    {{exception}}Collection exceptionCollection;
    {%for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%}
    try
    {
        auto obj = {{ObjectType(optionName, s)}}::FromJson(json);
        return {{className}}(obj);
    }
    catch (const std::exception& e)
    {
        // {{optionName}} wasnt able to deserialize{%if not loop.last%}
        // but there are other option to try{%endif%}
        exceptionCollection.AddException(e);
    }
    {%endfor %}

    // Didnt deserialize
    throw {{exception}}(exceptionCollection.what());
}

void {{className}}::ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const
{
    {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%}
    {%if not loop.first%}
    else {%endif%}if (_value.which() == {{loop.index - 1}})
    {
        boost::get<{{ObjectType(optionName, s)}}>(_value).ToJson(value, allocator);
    }
    {%-endfor %}
}

void {{className}}::SetHandle(const std::string& handle)
{
    _handle = handle;

    {%for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset-%}
    {%if not loop.first%}
    else {%endif%}if (_value.which() == {{loop.index - 1}})
    {
        boost::get<{{ObjectType(optionName, s)}}>(_value).SetHandle(handle);
    }
    {%-endfor %}
}

std::string {{className}}::GetHandle() const
{
    return _handle;
}